////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2009 Daniel F. Hanson
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
////////////////////////////////////////////////////////////////////////////////

using System.Collections.Generic;

namespace SilverLua.Compiler
{
   internal class ChunkScope
   {
      internal List<Instruction> Instructions { get; private set; }
      
      private List<BlockScope> _blockScopes = new List<BlockScope>();
      private Stack<BlockScope> _scopeStack = new Stack<BlockScope>();
      
      private ChunkScope _parentChunk;
      private BlockScope _parentBlock;

      private Dictionary<string, Symbol> _params = new Dictionary<string, Symbol>();
      private int _valueCount = 0;
      
      internal BlockScope CurrentBlock { get { return _scopeStack.Peek(); } }
      internal int RegisterCount { get { return _valueCount; } }
      internal List<Upvalue> Upvalues { get; private set; }
      
      internal Dictionary<string, int> _stringTable { get; private set; }
      
      private List<Symbol> _upvalueCache = new List<Symbol>();
      
      internal ChunkScope()
      {
         _parentChunk = null;
         _parentBlock = null;
         Instructions = new List<Instruction>();
         _stringTable = new Dictionary<string, int>();
         Upvalues = new List<Upvalue>();
         PushBlock();
      }
      
      internal ChunkScope(ChunkScope parentChunk)
      {
         _parentChunk = parentChunk;
         _parentBlock = parentChunk.CurrentBlock;
         Instructions = new List<Instruction>();
         _stringTable = new Dictionary<string, int>();
         Upvalues = new List<Upvalue>();
         PushBlock();
      }
      
      internal void Add(Instruction instruction)
      {
         Instructions.Add(instruction);
      }
      
      internal int Count { get { return Instructions.Count; } }
      
      internal Instruction this[int index]
      {
         get { return Instructions[index]; }
         set { Instructions[index] = value; }
      }
      
      internal void PushBlock()
      {
         BlockScope block;
         
         if (_scopeStack.Count > 0)
         {
            block = new BlockScope(_scopeStack.Peek());
         }
         else
         {
            block = new BlockScope();
         }
         
         _blockScopes.Add(block);
         _scopeStack.Push(block);
      }
      
      internal void PopBlock()
      {
         _scopeStack.Pop();
      }
      
      internal Symbol AddLocal(string name)
      {
         return _scopeStack.Peek().AddLocal(this, name, _valueCount++);
      }
      
      internal Symbol AddParameter(string name, int index)
      {
         Symbol symbol = new Symbol(this, name, OperandType.Parameter, index);
         _params[name] = symbol;
         return symbol;
      }
      
      internal int AllocateTempRegister()
      {
         return _valueCount++;
      }
      
      internal int AllocateTempRegisters(int count)
      {
         int index = _valueCount;
         _valueCount += count;
         return index;
      }
      
      internal string[] GetStringTable()
      {
         string[] stringTable = new string[_stringTable.Count];

         foreach (KeyValuePair<string, int> stringEntry in _stringTable)
         {
            stringTable[stringEntry.Value] = stringEntry.Key;
         }

         return stringTable;
      }
      
      internal int ResolveString(string str)
      {
         int index;

         if (!_stringTable.TryGetValue(str, out index))
         {
            index = _stringTable.Count;
            _stringTable.Add(str, index);
         }

         return index;
      }
      
      internal Symbol ResolveName(string name, List<ChunkScope> chunkList)
      {
         return ResolveName(name, chunkList, _scopeStack.Peek());
      }

      private Symbol ResolveName(string name, List<ChunkScope> chunkList, BlockScope startBlock)
      {
         Symbol symbol;

         if (!startBlock.ResolveName(name, out symbol) &&
             !_params.TryGetValue(name, out symbol))
         {
            if (_parentChunk != null)
            {
               symbol = _parentChunk.ResolveName(name, chunkList, _parentBlock);
            }
            else
            {
               symbol = new Symbol(null, name, OperandType.StringLiteral, -1);
            }
         }
         
         // Check for upvalue symbol
         if (symbol.Chunk != null &&
             symbol.Chunk != this)
         {
            Upvalue upvalue = new Upvalue { Type = symbol.Type,
                                            Register = symbol.Register };
            int index = Upvalues.IndexOf(upvalue);
            
            if (index == -1)
            {
               Upvalues.Add(upvalue);
               symbol = new Symbol(this, symbol.Name, OperandType.Upvalue, Upvalues.Count - 1);
               _upvalueCache.Add(symbol);
            }
            else
            {
               symbol = _upvalueCache[index];
            }
         }

         return symbol;
      }
   }
}
